package de.mizi.controller;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.BorderFactory;
import javax.swing.JLayeredPane;

import de.mizi.tilemap.factory.TilemapObjectFactory;
import de.mizi.tilemap.renderer.TilemapObjectRenderer;
import de.mizi.tilemap.renderer.TilemapRenderer;
import de.mizi.tilemap.renderer.TilemapViewport;
import de.mizi.ui.messagebox.MessageBox;
import de.mizi.ui.statusbox.LabelOnlyContent;
import de.mizi.ui.statusbox.SingleValueContent;
import de.mizi.ui.statusbox.StatusBox;
import de.mizi.ui.statusbox.TwoValuesContent;
import de.mizi.utility.FontData;

/**
 * This controller handles the game actions like player movement.
 * @author mizi
 *
 */
public class GameController extends JLayeredPane implements KeyListener {
	private GameData gameData;
	private TilemapRenderer mapRenderer;
	private TilemapViewport viewport;
	private MessageBox messageBox;
	private Rectangle messageBoxDefaultSize;
	private Rectangle messageBoxExtendedSize;
	private StatusBox statusBox;
	private int playerX;
	private int playerY;
	private int cursorX;
	private int cursorY;
	private int turns;
	private boolean isMessageBoxExtended;
	private boolean isLookModeEnabled;

	private static final int MESSAGE_BOX_DEFAULT_LINES = 5;
	private static final int MESSAGE_BOX_EXTENDED_LINES = 36;
	private static final Integer MESSAGE_BOX_DEFAULT_LAYER = JLayeredPane.DEFAULT_LAYER;
	private static final Integer MESSAGE_BOX_EXTENDED_LAYER = new Integer(20);
	
	private static final long serialVersionUID = 1L;
	
	public GameController() {
		FontData fontData = new FontData(new Font("Courier New", Font.PLAIN, 16), Color.WHITE);
		playerX = 1;
		playerY = 1;
		turns = 0;
		// Initializing the MapRenderer.
		TilemapObjectRenderer tileRenderer = new TilemapObjectRenderer(fontData);
		viewport = new TilemapViewport(0, 0, 39, 31);
		mapRenderer = new TilemapRenderer(tileRenderer);
		mapRenderer.setViewport(viewport);
		mapRenderer.setBackground(Color.BLACK);
		// Bottom and right border.
		mapRenderer.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 1, Color.WHITE));
		// Initializing the StatusBox.
		statusBox = new StatusBox(fontData);
		statusBox.setPreferredSize(new Dimension(200, mapRenderer.getHeight()));
		statusBox.addContent("HEADLINE", new LabelOnlyContent(65, 0, "Status"));
		statusBox.addContent("POSITION", new TwoValuesContent(0, 30, "Position: ", playerX, playerY, "; "));
		statusBox.addContent("TURNS", new SingleValueContent(0, 50, "Schritte: ", turns));
		statusBox.addContent("HELP1", new LabelOnlyContent(0, 200, "Pfeiltasten -> laufen"));
		statusBox.addContent("HELP2", new LabelOnlyContent(0, 220, "p -> Nachrichten an / aus"));
		statusBox.addContent("HELP3", new LabelOnlyContent(0, 240, "x -> umschauen an / aus"));
		statusBox.addContent("HELP4", new LabelOnlyContent(0, 260, "ESC -> beenden"));
		statusBox.setBackground(Color.BLACK);
		// Bottom border.
		statusBox.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.WHITE));
		// Initializing the MessageBox.
		messageBox = new MessageBox(fontData, MESSAGE_BOX_DEFAULT_LINES, MESSAGE_BOX_EXTENDED_LINES);
		messageBox.setBackground(Color.BLACK);
		
		int mapWidth = (int)mapRenderer.getPreferredSize().getWidth();
		int mapHeight = (int)mapRenderer.getPreferredSize().getHeight();
		int statusBoxWidth = 200;
		// Computing all necessary values for switching the MessageBox mode.
		messageBox.setVisibleLines(MESSAGE_BOX_EXTENDED_LINES);
		messageBoxExtendedSize = new Rectangle(0, 0, mapWidth + statusBoxWidth, messageBox.getMaximumHeight());
		messageBox.setVisibleLines(MESSAGE_BOX_DEFAULT_LINES);
		messageBoxDefaultSize = new Rectangle(0, mapHeight, mapWidth + statusBoxWidth, messageBox.getMaximumHeight());
		isMessageBoxExtended = false;
		// Set the initial bounds of each component.
		mapRenderer.setBounds(0, 0, mapWidth, mapHeight);
		statusBox.setBounds(mapWidth, 0, statusBoxWidth, mapHeight);
		messageBox.setBounds(messageBoxDefaultSize);
		
		add(mapRenderer, JLayeredPane.DEFAULT_LAYER);
		add(messageBox, JLayeredPane.DEFAULT_LAYER);
		add(statusBox, JLayeredPane.DEFAULT_LAYER);
		// The preferred size of this GameController.
		setPreferredSize(new Dimension(mapWidth + statusBoxWidth, mapHeight + messageBox.getHeight()));
		// Initializing the game.
		gameData = new GameData(new TilemapObjectFactory());
		gameData.addGameListener(mapRenderer);
		gameData.addGameListener(messageBox);
		gameData.addGameListener(statusBox);
		
		gameData.createNewMap();
		gameData.createNewPlayer(playerX, playerY);
		gameData.createNewCursor(0, 0);
		gameData.setShowCursor(false);
	}

	@Override
	public void keyPressed(KeyEvent e) {
		if(!isMessageBoxExtended) {
			if(e.getKeyCode() == KeyEvent.VK_UP) {
				if(isLookModeEnabled) {
					moveCursor(0, -1);
				} else {
					movePlayer(0, -1);
					updateMapViewport();
				}
			}
			if(e.getKeyCode() == KeyEvent.VK_DOWN) {
				if(isLookModeEnabled) {
					moveCursor(0, 1);
				} else {
					movePlayer(0, 1);
					updateMapViewport();
				}
			}
			if(e.getKeyCode() == KeyEvent.VK_RIGHT) {
				if(isLookModeEnabled) {
					moveCursor(1, 0);
				} else {
					movePlayer(1, 0);
					updateMapViewport();
				}
			}
			if(e.getKeyCode() == KeyEvent.VK_LEFT) {
				if(isLookModeEnabled) {
					moveCursor(-1, 0);
				} else {
					movePlayer(-1, 0);
					updateMapViewport();
				}
			}
			if(e.getKeyCode() == KeyEvent.VK_X) {
				switchLookMode();
			}
		}
		if(e.getKeyCode() == KeyEvent.VK_P) {
			switchMessageBoxMode();
		}
	}
	
	@Override
	public void keyReleased(KeyEvent e) {
	}
	
	@Override
	public void keyTyped(KeyEvent e) {
	}
	
	private void switchMessageBoxMode() {
		isMessageBoxExtended = !isMessageBoxExtended;
		if(isMessageBoxExtended) {
			messageBox.setVisibleLines(MESSAGE_BOX_EXTENDED_LINES);
			messageBox.setBounds(messageBoxExtendedSize);
			this.setLayer(messageBox, MESSAGE_BOX_EXTENDED_LAYER);
		} else {
			messageBox.setVisibleLines(MESSAGE_BOX_DEFAULT_LINES);
			messageBox.setBounds(messageBoxDefaultSize);
			this.setLayer(messageBox, MESSAGE_BOX_DEFAULT_LAYER);
		}
	}
	
	private void switchLookMode() {
		isLookModeEnabled = !isLookModeEnabled;
		gameData.setShowCursor(isLookModeEnabled);
		if(isLookModeEnabled) {
			cursorX = playerX;
			cursorY = playerY;
			gameData.setCursorPosition(cursorX, cursorY);
		}
	}
	
	private void moveCursor(int dx, int dy) {
		int newCursorX = cursorX + dx;
		int newCursorY = cursorY + dy;
		if(newCursorX < viewport.x || newCursorX >= viewport.x + viewport.widthInTiles || newCursorY < viewport.y || newCursorY >= viewport.y + viewport.heightInTiles) {
			return;
		}
		cursorX = newCursorX;
		cursorY = newCursorY;
		gameData.setCursorPosition(cursorX, cursorY);
	}
	
	private void movePlayer(int dx, int dy) {
		if(gameData.isWalkableAtPosition(playerX + dx, playerY + dy)) {
			playerX += dx;
			playerY += dy;
			gameData.setPlayerPosition(playerX, playerY);
			messageBox.addMessage("Spieler bewegt sich.");
			statusBox.setContentFirstValue("TURNS", ++turns);
			statusBox.setContentFirstValue("POSITION", playerX);
			statusBox.setContentSecondValue("POSITION", playerY);
		} else {
			messageBox.addMessage("Spieler kann sich nicht bewegen.");
		}
	}
	
	private void updateMapViewport() {
		int oldX = viewport.x;
		int oldY = viewport.y;
		int x = playerX - viewport.widthInTiles / 2;
		int y = playerY - viewport.heightInTiles / 2;
		viewport.x = Math.max(0, Math.min(x, gameData.getMapWidthInTiles() - viewport.widthInTiles));
		viewport.y = Math.max(0, Math.min(y, gameData.getMapHeightInTiles() - viewport.heightInTiles));
		if(viewport.x != oldX || viewport.y != oldY) {
			mapRenderer.setViewportPosition(viewport.x, viewport.y);
		}
	}
}
